package cn.jhz.learn.community_dynamic.security.config;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.header.Header;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.validation.beanvalidation.CustomValidatorBean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import cn.jhz.learn.community_dynamic.dao.PermissionJpaRepository;
import cn.jhz.learn.community_dynamic.dao.RoleJpaRepository;
import cn.jhz.learn.community_dynamic.security.filter.JsonPhoneSmsCodeAuthenticationFilter;
import cn.jhz.learn.community_dynamic.security.filter.JsonUsernamePasswordAuthenticationFilter;
import cn.jhz.learn.community_dynamic.security.filter.JwtAuthenticationFilter;
import cn.jhz.learn.community_dynamic.security.filter.OptionsRequestFilter;
import cn.jhz.learn.community_dynamic.security.handler.TokenClearLogoutHandler;
import cn.jhz.learn.community_dynamic.security.provider.JwtAuthenticationProvider;
import cn.jhz.learn.community_dynamic.security.service.JsonUserDetailsService;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JsonUserDetailsService jsonUserDetailsService;
    @Autowired
    private TokenClearLogoutHandler tokenClearLogoutHandler;
    @Autowired
    private OptionsRequestFilter optionsRequestFilter;
    private JsonLoginDsl jsonLoginDsl;
    private JwtRefreshDsl jwtRefreshDsl;
    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;
    @Autowired
    @Lazy
    private JsonPhoneSmsCodeAuthenticationFilter jsonPhoneSmsCodeAuthenticationFilter;
    @Autowired
    @Lazy
    private JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter;
    @Autowired
    @Lazy
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
	this.jsonLoginDsl = JsonLoginDsl.jsonLoginDsl(this.jsonUsernamePasswordAuthenticationFilter,
		this.jsonPhoneSmsCodeAuthenticationFilter);
	this.jwtRefreshDsl = JwtRefreshDsl.jwtRefreshDsl(jwtAuthenticationFilter);
	
	
	http.csrf().disable()
		.formLogin().disable()
		.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
//		.sessionManagement().disable()
		.cors().and().headers()
		.addHeaderWriter(new StaticHeadersWriter(
			Arrays.asList(
				new Header("Access-control-Allow-Origin", "*"),
				new Header("Access-Control-Expose-Headers", "Authorization")
				)))
		.and().addFilterAfter(this.optionsRequestFilter, CorsFilter.class).apply(this.jsonLoginDsl)
		.and().apply(this.jwtRefreshDsl)
		.and().logout()
		// .logoutUrl("/logout") //默认就是"/logout"
		.addLogoutHandler(this.tokenClearLogoutHandler)
		.logoutSuccessHandler(httpStatusReturningLogoutSuccessHandler())
		.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.authenticationProvider(daoAuthenticationProvider()).authenticationProvider(this.jwtAuthenticationProvider);
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
	CorsConfiguration configuration = new CorsConfiguration();
	configuration.setAllowedOrigins(Arrays.asList("*"));
	configuration.setAllowedMethods(Arrays.asList("GET", "POST", "HEAD", "OPTION", "PATCH", "PUT", "DELETE"));
	configuration.setAllowedHeaders(Arrays.asList("*"));
	configuration.addExposedHeader("Authorization");
	UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
	source.registerCorsConfiguration("/**", configuration);
	return source;
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
	return super.authenticationManagerBean();
    }

    @Bean
    protected CustomValidatorBean customValidatorBean() {
	return new CustomValidatorBean();
    }

    @Bean
    protected HttpStatusReturningLogoutSuccessHandler httpStatusReturningLogoutSuccessHandler() {
	return new HttpStatusReturningLogoutSuccessHandler();
    }

    @Bean
    protected AuthenticationProvider daoAuthenticationProvider() {
	// 这里会默认使用BCryptPasswordEncoder比对加密后的密码，注意要跟createUser时保持一致
	DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
	daoProvider.setUserDetailsService(this.jsonUserDetailsService);
	return daoProvider;
    }

    @Bean("accountLoginAntPathRequestMatcher")
    protected AntPathRequestMatcher accountLoginAntPathRequestMatcher() {
	return new AntPathRequestMatcher("/api/*/user/account_login", "POST");
    }

    @Bean("phoneLoginAntPathRequestMatcher")
    protected AntPathRequestMatcher phoneLoginAntPathRequestMatcher() {
	return new AntPathRequestMatcher("/api/*/user/phone_login", "POST");
    }

    @Bean("permissiveRequestMatchers")
    protected List<RequestMatcher> permissiveRequestMatchers(PermissionJpaRepository permissionJpaRepository, RoleJpaRepository roleJpaRepository
            , RequestMappingHandlerMapping requestMappingHandlerMapping) {
	List<RequestMatcher> matchers =
	permissionPreProcessor(permissionJpaRepository, roleJpaRepository, requestMappingHandlerMapping).getResourcesList()
		.stream()
		.map(permission->{
		    return new AntPathRequestMatcher(permission.getResource());
		})
		.collect(Collectors.toList());
	matchers.addAll(Stream.of("/logout").map(url -> new AntPathRequestMatcher(url)).collect(Collectors.toList()));
	return matchers;
    }
    
    @Bean
    protected ObjectMapper objectMapper() {
	return new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    }
    
    @Bean
    public PermissionPreProcessor permissionPreProcessor(PermissionJpaRepository permissionJpaRepository, RoleJpaRepository roleJpaRepository
            , RequestMappingHandlerMapping requestMappingHandlerMapping){
        return new PermissionPreProcessor(permissionJpaRepository, roleJpaRepository, requestMappingHandlerMapping);
    }
}
